from fastapi.security.utils import get_authorization_scheme_param
from datetime import datetime, timedelta
from jose import jwt
from fastapi import Depends, Request
from asgiref.sync import sync_to_async

from fastapi.security import HTTPBearer
from passlib.context import CryptContext
from sqlalchemy.ext.asyncio import AsyncSession
from app.common.exception.errors import TokenError
from app.core.conf import settings
from app.crud.user import user_dao
from app.models.user import User


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

DependsJwtAuth = Depends(HTTPBearer())


@sync_to_async
def get_hash_password(password: str) -> str:
    return pwd_context.hash(password)


@sync_to_async
def password_verify(plain_password, hashed_password) -> bool:
    return pwd_context.verify(plain_password, hashed_password)


async def create_access_token(
    sub: str, expires_delta: timedelta | None = None, **kwargs
) -> tuple[str, datetime]:
    if expires_delta:
        expire = datetime.now() + expires_delta
        expire_seconds = int(expires_delta.total_seconds())
    else:
        expire = datetime.now() + timedelta(
            seconds=settings.access_token_expire_seconds
        )
        expire_seconds = settings.access_token_expire_seconds
    to_encode = {"exp": expire, "sub": sub, **kwargs}
    token = jwt.encode(to_encode, settings.secrect_key, settings.algorithm)

    # todo 添加token到redis
    return token, expire


@sync_to_async
def get_token(request: Request):
    authorization = request.headers.get("Authorization")
    scheme, token = get_authorization_scheme_param(authorization)
    if not authorization or scheme.lower() != "bearer":
        raise TokenError(msg="token无效")
    return token


@sync_to_async
def jwt_decode(token: str) -> int:
    try:
        payload = jwt.decode(token, settings.secrect_key, settings.algorithm)
        user_id = int(payload.get("sub"))
        if not user_id:
            raise TokenError(msg="Token 无效")
    except jwt.ExpiredSignatureError:
        raise TokenError(msg="Token 已过期")
    except (jwt.JWTError, Exception):
        raise TokenError(msg="Token 无效")
    return user_id


async def jwt_authentication(token: str) -> dict[str, int]:
    user_id = await jwt_decode(token)
    return {"sub": user_id}


async def get_current_user(db: AsyncSession, data: dict) -> User:
    """
    Get the current user through token

    :param db:
    :param data:
    :return:
    """
    user_id = data.get("sub")
    user = await user_dao.get(db, pk=user_id)
    if not user:
        raise TokenError(msg="Token 无效")
    return user
